#include "main.h"

/*-----------------------------------------------------------------------------
 *  global variable
 *-----------------------------------------------------------------------------*/
gchar *log_file = NULL;
static gchar *ip_file = NULL;
static gchar *op_file = NULL;
static gchar *stdin_str = NULL;
static GOptionEntry entries[] =
{
  { "input", 'i', 0, \
    G_OPTION_ARG_FILENAME, &ip_file, "input file with command syntax", "file name" },
  { "stdin", 's', 0, \
    G_OPTION_ARG_STRING, &stdin_str, "input string with command sytax", "string" },
  { "output", 'o', 0, \
    G_OPTION_ARG_FILENAME, &op_file, "output file generate by this tool", "file name" },
  { "log", 'l', 0, \
    G_OPTION_ARG_FILENAME, &log_file, "specify where to report the log", "file name" },
  { NULL }
};

static GSList *root = NULL;


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  main
 *  Description:  main entry
 * =====================================================================================
 */
int main(int args, char *argv[])
{
  // arguments process
  if (setup_options (args, argv))
    return (1);

  // decide log to file or log nothing
  // remove log file if it exist
  if (log_file) {
    if (g_file_test (log_file, G_FILE_TEST_IS_REGULAR)) {
      if (g_remove (log_file)) {
        g_print ("can't remove old log\n");
        return (1);
      }
    }
    // redirect all the log to @log_file
    g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG, log_redirect, NULL);
  } else {
    g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG, log_dummy_redirect, NULL);
  }


  // decide input from file or stdin
  if (ip_file) {
    if (!g_file_test (ip_file, G_FILE_TEST_IS_REGULAR)) {
      g_print ("Can't find input file %s\n", ip_file);
      return 1;
    }
  }

  // check expression before parse
  if (ip_file) {
    if (check_expr (ip_file)) {
      g_debug ("[main]: check_expr failed\n");
      return 1;
    }
  }

  if (stdin_str) {
    GString *expr = g_string_new (stdin_str);
    if (check_expr_each (expr)) {
      g_string_free (expr, TRUE);
      return 1;
    }
    g_string_free (expr, TRUE);
  }

  // parse expression
  if (ip_file) {
    if (parse_expr (ip_file)) {
      g_debug ("[main]: parse_expr failed\n");
      return 1;
    }
  }

  if (stdin_str) {
    GString *expr = g_string_new (stdin_str);
    parse_expr_each (expr);
    g_string_free (expr, TRUE);
  }


  g_free (ip_file);
  g_free (op_file);
  g_free (log_file);

  return 0;
}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  parse_expr
 *  Description:  
 *  Return: 0 success, > 1 failed
 * =====================================================================================
 */
gint parse_expr (gchar *ip_file)
{
  GError *error = NULL;
  GIOChannel *ip = g_io_channel_new_file (ip_file, "r", &error);

  if (!ip) {
    g_print ("Can't open file %s to read: %s\n", ip_file, error->message);
    g_error_free (error);
    return 2;
  }

  // begin check
  gsize ter_pos;
  gint find_error = 0;
  GString *expr = g_string_new (NULL);
  while (1) {
    GIOStatus status = g_io_channel_read_line_string (ip, expr, &ter_pos, &error);
    if (status == G_IO_STATUS_EOF) {
      // end
      break;
    } else if (status == G_IO_STATUS_NORMAL) {
      // check expression one by one
      g_string_truncate (expr, ter_pos);
      parse_expr_each (expr);
      whole_root_free (&root);
    } else {
      g_print ("Can't read line from file %s\n: %s", ip_file, error->message);
      g_error_free (error);
      find_error = 1;
      break;
    }
  }

  g_string_free (expr, TRUE);
  g_io_channel_shutdown (ip, TRUE, NULL);

  if (find_error)
    return 1;

  return 0;
}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  parse_expr_each
 *  Description:  
 * =====================================================================================
 */
void parse_expr_each (const GString *expr)
{
  g_return_if_fail (expr);

  if (parse_tree (&root, expr)) {
    return;
  }

  tree_grow (root);

  harvest_fruit (root);

  debug_print_root (root);
}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  whole_root_free
 *  Description:  
 * =====================================================================================
 */
void whole_root_free (GSList **root)
{
  GSList *list = *root;
  while (list) {
    GSList *next = list->next;

    TreeAttr *tree = list->data;
    g_slist_free (tree->head);
    g_string_free (tree->expr, TRUE);
    g_free (tree);

    list = next;
  }

  g_slist_free (*root);
  *root = NULL;
}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  setup_options
 *  Description:  setup and parse the command's options
 *  Return: 0 success, 1 parser error
 * =====================================================================================
 */
gint setup_options (int args, char *argv[])
{
  GOptionContext *context;
  GError *error = NULL;

  // setup options
  context = g_option_context_new ("- Command Syntax Parser");
  g_option_context_add_main_entries (context, entries, NULL);
  g_option_context_set_summary (context, "Command Syntax Parser(CSP) is tool to parse the command option syntax");
  g_option_context_set_description (context, "Bug report Address: pqs-srp@groups.hp.com");

  // parse input
  if (!g_option_context_parse (context, &args, &argv, &error))
  {
      g_print ("option parsing failed: %s\n", error->message);
      g_error_free (error);
      return 1;
  }

  // input file or stdin should exist at least one
  if (!ip_file && !stdin_str) {
    g_print ("\nThe input file or stdin should exist at least.\
        \n\nuse -h or --help to see help message\n");
    return 1;
  }

  g_option_context_free (context);

  return 0;
}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  tree_grow
 *  Description:  parse the tree and grow up.
 * =====================================================================================
 */
void tree_grow (GSList *root)
{
  g_slist_foreach (root, tree_grow_each, NULL);

  g_debug ("remove duplicate----->\n");
  remove_dup (root);

  // copy expression from expr to list which length == 0
  gint i = 0;
  for (i = 0; i < g_slist_length (root); i++) {
    TreeAttr *attr = g_slist_nth_data (root, i);
    if (!g_slist_length (attr->head)) {
      GString *str = g_string_new (attr->expr->str);
      attr->head = g_slist_append (attr->head, str);
    }
  }


  debug_print_root(root);
}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  tree_grow_each
 *  Description:  make tree grow, callback function
 * =====================================================================================
 */
void tree_grow_each (gpointer data, gpointer user_data)
{
  gint end = 0;
  TreeAttr *attr = data;

  // iteration exit condition
  if (attr->type == TREE_STATIC) {
    return;
  }

  // first round parse
  if (!attr->head) {
    g_debug ("[tree_grow_each]expr %d: mutex first round\n", attr->index);
    parse_mutex (attr);
  }

  if (!attr->head) {
    g_debug ("[tree_grow_each]expr %d: brace first round\n", attr->index);
    parse_brace (attr);
  }

  if (!attr->head) {
    g_debug ("[tree_grow_each]expr %d: curly first round\n", attr->index);
    parse_curly (attr);
  }

  // only one round for token angle
  if (!attr->head) {
    g_debug ("[tree_grow_each]expr %d: angle first round\n", attr->index);
    parse_angle (attr);
  }

  g_debug ("iteration Begin ------------------>\n");
  // iteration parse
  do {
    end = parse_mutex (attr);
  } while (!end);

  do {
    end = parse_brace (attr);
  } while (!end);

  do {
    end = parse_curly (attr);
  } while (!end);


  debug_print_root(root);

  attr->type = TREE_STATIC;

}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  remove_dup
 *  Description:  
 * =====================================================================================
 */
void remove_dup (GSList *root)
{
  GSList *list = root;

  while (list) {
    GSList *next = list->next;

    TreeAttr *attr = list->data;
    GSList *list2 = attr->head;

    gint i, find = 0;
    while (1) {
      gint len = g_slist_length (list2);
      for (i = 0; i < len; i++) {
        GSList *cur = g_slist_nth (list2, i);
        // remove all the element which len = 0
        if (!((GString *)cur->data)->len) {
          g_debug ("[remove_dup]expr %d:remove index = %d", attr->index, i);
          list2 = g_slist_delete_link (list2, cur);
          find = 1;
          break;
        }
      }

      if (i == len)
        break;

    }

    if (find) {
      GString *str = g_string_new(NULL);
      list2 = g_slist_append (list2, str);
    }

    attr->head = list2;

    list = next;
  }
}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  harvest_fruit
 *  Description:  options combination
 * =====================================================================================
 */
void harvest_fruit (GSList *root)
{
  GSList *list = root;
  TreeAttr *tree = list->data;

  // init all the map to 1
  map_init (root);
  tree->point = 1;

  // traversal the tree and set bit one by one
  gint i = 0;
  do {
    g_debug ("[harvest_fruit]: index = %d", i++);
    debug_print_map (root);

    GString *basket = pick_up_fruit (root);
    if (!basket)
      g_print ("basket == null\n");

    basket_strip (basket);

    if (report_expr (op_file, basket)) {
      g_debug ("[harvest_fruit]: report_expr failed");
      return;
    }
    //g_string_free (basket, TRUE);
  } while (!map_mask (root));

}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  pick_up_fruit
 *  Description:  
 * =====================================================================================
 */
GString *pick_up_fruit (GSList *root)
{
  gint i = 0;
  GString *basket = g_string_new (NULL);

  // traversal the tree and pick up fruit one by one.
  for (i = 0; i < g_slist_length (root); i++) {
    TreeAttr *cur = g_slist_nth_data (root, i);
    gint len = g_slist_length (cur->head);
    
    gint pos = map_get_pos_at (root, cur->index);
    GString *str = g_slist_nth_data (cur->head, pos);
    if (!str) {
      g_debug ("Fatal error");
      return NULL;
    }

    //g_debug ("[pick_up_fruit]expr %d: pos = %d str = (%s)", cur->index, pos, str->str);
    g_string_append (basket, str->str);
  }

  g_debug ("\n[pick_up_fruit]: str = (%s)\n", basket->str);

  return basket;
}


/* 
 * ===  FUNCTION  ======================================================================
 *         Name:  report_expr
 *  Description:  
 * =====================================================================================
 */
gint report_expr (gchar *op_file, const GString *expr)
{
  // if no out file specified, then print to stdout
  if (!op_file) {
    g_print ("%s\n", expr->str);
    return 0;
  }

  // or write to file
  GError *error = NULL;
  GIOChannel *op = g_io_channel_new_file (op_file, "a+", &error);

  if (!op) {
    g_print ("Can't open file %s to write: %s\n", op_file, error->message);
    g_error_free (error);
    return 2;
  }

  // begin write
  gsize written;
  GIOStatus status = g_io_channel_write_chars (op, expr->str, expr->len, &written, &error);

  if (status != G_IO_STATUS_NORMAL || expr->len != written) {
    g_print ("write line to file %s error: %s", op_file, error->message);
    g_error_free (error);
    g_io_channel_shutdown (op, TRUE, NULL);
    return 1;
  }

  g_io_channel_shutdown (op, TRUE, NULL);

  return 0;
}
